#!/usr/bin/python

# License:
#
#	PyGnosis, a tiny MVC framework with a stateless message bus
#    	Copyright (C) 2011  Daniel Azulay
#
#	This program is free software: you can redistribute it and/or modify
#    	it under the terms of the GNU General Public License as published by
#    	the Free Software Foundation, either version 3 of the License, or
#    	(at your option) any later version.
#
#    	This program is distributed in the hope that it will be useful,
#    	but WITHOUT ANY WARRANTY; without even the implied warranty of
#    	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    	GNU General Public License for more details.
#
#    	You should have received a copy of the GNU General Public License
#    	along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
# Information:
#
#	PyGnosis is a light weight MVC framework for rapid application development in python.
#	It features a pseudo-messagebus and holds a number of classes 
#	that make adhering to the MVC paradigm a piece of cake.
#
#	Use the PyGnosis class to set your application globals and use the Model and View classes 
#	to implement your application.
#
#	The View is currently set up to drop the user to a command shell, but you can replace
#	the call to Shell() by your own view implementation. The only variable that the Shell
# 	class can directly modify is the global DEBUG which sets debug mode on or off. If you
# 	plan on expanding the command library, please have each keyword trigger a call to
# 	objMessageBus.broadcast() with the message you wish to broadcast as string.
# 	The MessageBus instance will automatically notify listening classes as a result. All
# 	you need to do is write code in the appropriate classes to interpret incoming messages.
#


import sys, string, gc, tkinter

class Skeleton:

	def __init__(self):

		global objSkeleton

		Notification.notify(False, ' '.join([str(self.__class__), 'initializing...']), MSG_DBG)


	def process(self, message):

		Notification.notify(False, ' '.join([str(self.__class__), 'received message:', message]), MSG_DBG)

		messageKey = message.split(' ')

		if messageKey[0] == 'skeleton':

			if messageKey[1] == 'enable':
			
				pass

			elif messageKey[1] == 'disable':

				self.disable()

		else:

			Notification.notify(False, ' '.join([str(self.__class__), 'not interested.']), MSG_DBG)

			# Add code here to process incoming messages


	def disable(self):

		Notification.notify(False, ' '.join([str(self.__class__), 'disabling...']), MSG_DBG)

		try:
		
			del objSkeleton
	
		except(UnboundLocalError):

			pass

	

class Model:

	# If you change a value in your model, broadcast it to the message bus by calling objMessageBus.broadcast() with the message to send as argument.

	def __init__(self):

		global objModel

		Notification.notify(False, ' '.join([str(self.__class__), 'initializing...']), MSG_DBG)

		# Set up your model here


	def process(self, message):

		Notification.notify(False, ' '.join([str(self.__class__), 'received message:', message]), MSG_DBG)

		messageKey = message.split(' ')

		if messageKey[0] == 'model':

			if messageKey[1] == 'enable':
			
				pass

			elif messageKey[1] == 'disable':

				self.disable()

		else:

			Notification.notify(False, ' '.join([str(self.__class__), 'not interested.']), MSG_DBG)

			# Add code here to process incoming messages

	def disable(self):

		Notification.notify(False, ' '.join([str(self.__class__), 'disabling...']), MSG_DBG)

		try:
		
			del objModel
	
		except(UnboundLocalError):

			pass


class View:

	# If you change a value in your view, broadcast it to the message bus by calling objMessageBus.broadcast() with the message to send as argument.

	def __init__(self):

		global objView, objShell, SHELL_ENABLE

		Notification.notify(False, ' '.join([str(self.__class__), 'initializing...']), MSG_DBG)

		# Set up your view here


	def process(self,message):

		Notification.notify(False, ' '.join([str(self.__class__), 'received message:', message]), MSG_DBG)

		messageKey = message.split(' ')

		if messageKey[0] == 'view':

			if messageKey[1] == 'enable':

				if SHELL_ENABLE == True:

					objMessageBus.broadcast('shell enable')

				else:

					objWinMain = UI()

			elif messageKey[1] == 'disable':

				self.disable()

		else:

			Notification.notify(False, ' '.join([str(self.__class__), 'not interested.']), MSG_DBG)

			# Add code here to process incoming messages

	def disable(self):

		Notification.notify(False, ' '.join([str(self.__class__), 'disabling...']), MSG_DBG)

		try:
		
			del objView
	
		except(UnboundLocalError):

			pass


class UI:

	def __init__(self):

		windowMain = tkinter.Tk()

		canvas = tkinter.Canvas(windowMain, width=1024, height=600, colormap='new')
		canvas.pack()

		windowMain.title('PyGnosis')
		windowMain.mainloop()


class Shell:

	def __init__(self):

		global objShell, objPyGnosis
	
		Notification.notify(False, ' '.join([str(self.__class__), 'initializing...']), MSG_DBG)

		self.result = False


	def process(self, message):

		Notification.notify(False, ' '.join([str(self.__class__), 'received message:', message]), MSG_DBG)

		messageKey = message.split(' ')

		if messageKey[0] == 'shell':

			if messageKey[1] == 'enable':
			
				self.enable()

			elif messageKey[1] == 'disable':

				self.disable()

		else:

			Notification.notify(False, ' '.join([str(self.__class__), 'not interested.']), MSG_DBG)


	def enable(self):

		Notification.notify(False, ' '.join(['command line given:', str(sys.argv), '\n']), MSG_DBG)

		Notification.notify(False, LICENSE, MSG_CLR)

		showHelpMessage = True

		while self.result == False:

			if len(sys.argv) > 1:

				del sys.argv[0]

				command = ' '.join(sys.argv) # process command line arguments first

			else:

				if showHelpMessage:

					Notification.notify(False, "Welcome to the PyGnosis command shell.\nType '(h)elp' for a list of commands or '(q)uit' to quit.\n" , MSG_CLR)
					
					showHelpMessage = False

				command = input('> ') # prompt the user for input when there are no more command line arguments

			self.work(command)


	def work(self, command = ''):

		commandString = command.split()

		print(commandString)

		try:

			firstCommand = commandString[0]

		except(IndexError):

			firstCommand = ''

		try:

			commandKey = commandString[1]

		except(IndexError):

			commandKey = ''

		try:

			commandValue = commandString[2]

		except(IndexError):

			commandValue = ''


		if firstCommand == 'quit' or firstCommand == 'q':

			self.result = True

			objPyGnosis.disable()

		elif firstCommand == 'help' or firstCommand == 'h':

			self.showHelp(commandKey)

		elif command == 'acme':

			Notification.notify(False, "I'm hunting wabbits!\n", MSG_CLR)

		elif firstCommand == 'broadcast':

			message = ' '.join([commandKey, commandValue])

			objMessageBus.broadcast(message)

		elif firstCommand == 'do':

			message = ' '.join([firstCommand, commandKey])

			objMessageBus.broadcast(message)

		# Add code here to do work based on what the message contains by adding more clauses to the if-elif chain.

		else:

			pass


	def showHelp(self, subject):

		if subject == '':

			Notification.notify(False, HELP, MSG_CLR)


	def disable(self):

		Notification.notify(False, ' '.join([str(self.__class__), 'disabling...']), MSG_DBG)

		try:

			self.result = True
	
		except(UnboundLocalError):

			pass
			

class Controller:

	def __init__(self):

		global objMessageBus, objModel, objView, objController, DEBUG

		Notification.notify(False, ' '.join([str(self.__class__), 'initializing...']), MSG_DBG)


	def toggleDebug(self, debug):

		global DEBUG

		DEBUG = debug
		
		message = ' '.join(['debug set to:',  str(DEBUG)])

		Notification.notify(False, message, MSG_SYS)

	
	def process(self, message):

		Notification.notify(False, ' '.join([str(self.__class__), 'received message: ', message]), MSG_DBG)

		messageKey = message.split()

		if messageKey[0] == 'debug':

			if messageKey[1] == 'on':

				self.toggleDebug(True)				

			elif messageKey[1] == 'off':

				self.toggleDebug(False)

			else:

				Notification.notify(False, 'invalid setting', MSG_ERR)

				
		elif messageKey[0] == 'disable':

			self.disable()			

		elif messageKey[0] == 'controller':

			if messageKey[1] == 'enable':
			
				pass				

			elif messageKey[1] == 'disable':

				self.disable()

		elif messageKey[0] == 'do':

			try:
		
				exec(str(messageKey[1]))

			except:

				Notification.notify(False, str(sys.exc_info()[0]), MSG_ERR)

		else:

			Notification.notify(False, ' '.join([str(self.__class__), 'not interested.']), MSG_DBG)


	def disable(self):
		
		Notification.notify(False, ' '.join([str(self.__class__), 'disabling...']), MSG_DBG)

		try:
		
			del objController
	
		except(UnboundLocalError):

			pass


class MessageBus:

	def __init__(self):

		global objMessageBus, objects

		try:

			objMessageBus

		except(NameError):

			objMessageBus = self

		Notification.notify(False, ' '.join([str(self.__class__), ' initializing...']), MSG_DBG)


	def broadcast(self, message): # Broadcast a message and and update model and view accordingly

		for i in objects:

			try:

				Notification.notify(False, ' '.join([str(self.__class__), 'sending message', message, 'to', str(i.__class__)]), MSG_DBG)

				i.process(message)

			except (AttributeError):

				Notification.notify(False, ' '.join([str(self.__class__), 'tried to send a message to a non-existent object']), MSG_WNG)

			continue


	def process(self, message):

		messageKey = message.split(' ')

		if messageKey[0] == 'messagebus':

			if messageKey[1] == 'enable':
			
				pass

			elif messageKey[1] == 'disable':

				self.disable()

		else:

			Notification.notify(False, ' '.join([str(self.__class__), 'not interested.']), MSG_DBG)
	
	def disable(self):

		Notification.notify(False, ' '.join([str(self.__class__), 'disabling...']), MSG_DBG)

		try:
		
			del objMessageBus
	
		except(UnboundLocalError):

			pass

	
class Notification:

	# The notification system has been intentionally separated from the view because we 
	# need to inform the user of what's going on and/or log events even if the view runs into trouble.

	def __init__(self):

		Notification.notify(False, ' '.join([str(self.__class__), ' initializing...']), MSG_DBG)
		
		
	def notify(popup, message, messageType = 6): # Output a message to the console

		if not (DEBUG == False and messageType == MSG_DBG):

			notification = {
				1: 'System: ' + message,
				2: 'Debug: ' + message,
				3: 'Error: ' + message,
				4: 'Warning: ' + message,
				5: 'Notice: ' + message,
				6: message
			}.get(messageType) # Format the message appropriately

			if popup == True: # Display a popup window

				# Add code here to show a popup window

				pass
			
			else: # Output to the console
	
				print(notification)


class PyGnosis:

	def __init__(self):

		# Global definitions
		global MSG_SYS, MSG_DBG, MSG_ERR, MSG_WNG, MSG_NOT, MSG_CLR
		global APP_NAME, APP_AUTHOR, APP_VERSION, APP_TIMESTAMP
		global DEBUG, SHELL_ENABLE

		global LICENSE, HELP

		# Help text
		HELP = open('./help', 'r').read()

		# License header
		LICENSE = open('./license', 'r').read()

		# Enable or disable debug mode
		DEBUG = True
		SHELL_ENABLE = True # For now, we will drop the user to a command shell.

		# Message types for the logger
		MSG_SYS = 1
		MSG_DBG = 2
		MSG_ERR = 3 
		MSG_WNG = 4
		MSG_NOT = 5
		MSG_CLR = 6

		Notification.notify(False, 'Starting...\n', MSG_CLR)

		global objModel, objView, objController, objMessageBus, objShell, objPySMVC, objects

		if SHELL_ENABLE == True:

			objShell = Shell()

		else:

			objShell = None

		objModel = Model()
		objView = View()
		objController = Controller()
		objMessageBus = MessageBus()

		objects = [objShell, objModel, objView, objMessageBus, objController]

		objPyGnosis = self

		self.enable()

		Notification.notify(False, 'Exiting...\n', MSG_CLR)

	def enable(self):

		objMessageBus.broadcast('view enable')
		objMessageBus.broadcast('model enable')

	def disable(self):

		objMessageBus.broadcast('model disable')
		objMessageBus.broadcast('view disable')
		objMessageBus.broadcast('controller disable')
		objMessageBus.broadcast('shell disable')
		objMessageBus.broadcast('messagebus disable')

		del self

		
